/*
 * Sylvain BERTRAND <digital.ragnarok@gmail.com>
 * code protected by GNU GPL v3
 */
#include <X11/Xlib.h>
#include <X11/extensions/shape.h>
#include <X11/extensions/sync.h>
#include <libxml/xinclude.h>
#include <glib.h>
#include "../nls.h"
#include "../toolkit/prop.h"
#include "../toolkit/display.h"
#include "../toolkit/xml.h"
#include "../toolkit/xqueue.h"
#include "window.h"
#include "geom.h"
#include "stacking.h"
#include "misc.h"
#include "client.h"
#include "wm.h"
#include "debug.h"
#include "screen.h"
#include "group.h"
#include "frame.h"
#include "grab.h"
#include "event.h"
#include "focus.h"
#include "config.h"
#include "place.h"
#include "moveresize.h"
#include "actions.h"
#include "mouse.h"
#include "focus_cycle.h"
#define CLIENT_EVENTMASK (PropertyChangeMask | StructureNotifyMask | ColormapChangeMask)
#define CLIENT_NOPROPAGATEMASK (ButtonPressMask | ButtonReleaseMask | ButtonMotionMask)
struct client_callback {
	wm_client_callback func;
	gpointer data;
};
GList *client_list = NULL;
static GSList *client_destroy_notifies = NULL;

static gboolean has_parent(struct wm_client *self)
{
	return self->parents != NULL;
}

static gboolean has_children(struct wm_client *self)
{
	return self->transients != NULL;
}

static gboolean has_relative(struct wm_client *self)
{
	return has_parent(self) || client_has_group_siblings(self) || has_children(self);
}

static gboolean focused(struct wm_client *self)
{
	return self == focus_client;
}

static void update_strut(struct wm_client *self)
{
	struct strut_partial strut;
	STRUT_PARTIAL_SET(strut, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0);
	if(!PARTIAL_STRUT_EQUAL(strut, self->strut)) {
		self->strut = strut;
		if(self->frame)
			screen_update_areas();
	}
}

static struct wm_client *search_focus_tree(struct wm_client *self)
{
	GSList *it;
	struct wm_client *ret;
	for(it = self->transients; it; it = g_slist_next(it)) {
		if(focused(it->data))
			return it->data;
		if((ret = search_focus_tree(it->data)))
			return ret;
	}
	return NULL;
}

static struct wm_client *search_focus_tree_full(struct wm_client *self)
{
	if(self->parents) {
		GSList *it;
		for(it = self->parents; it; it = g_slist_next(it)) {
			struct wm_client *c = it->data;
			if((c = search_focus_tree_full(c)))
				return c;
		}
		return NULL;
	} else {
		if(focused(self))
			return self;
		return search_focus_tree(self);
	}
}

static struct wm_client *search_focus_group_full(struct wm_client *self)
{
	GSList *it;
	if(self->group) {
		for(it = self->group->members; it; it = g_slist_next(it)) {
			struct wm_client *c = it->data;
			if(focused(c))
				return c;
			if((c = search_focus_tree(it->data)))
				return c;
		}
	} else if(focused(self))
		return self;
	return NULL;
}

void client_startup(void)
{
}

void client_shutdown(void)
{
}

static void client_call_notifies(struct wm_client *self, GSList *list)
{
	GSList *it;
	for(it = list; it; it = g_slist_next(it)) {
		struct client_callback *d = it->data;
		d->func(self, d->data);
	}
}

void client_add_destroy_notify(wm_client_callback func, gpointer data)
{
	struct client_callback *d = g_slice_new(struct client_callback);
	d->func = func;
	d->data = data;
	client_destroy_notifies = g_slist_prepend(client_destroy_notifies, d);
}

void client_remove_destroy_notify(wm_client_callback func)
{
	GSList *it;
	for(it = client_destroy_notifies; it; it = g_slist_next(it)) {
		struct client_callback *d = it->data;
		if(d->func == func) {
			g_slice_free(struct client_callback, d);
			client_destroy_notifies = g_slist_delete_link(client_destroy_notifies, it);
			break;
		}
	}
}

static void client_get_area(struct wm_client *self)
{
	XWindowAttributes wattrib;
	Status ret;
	ret = XGetWindowAttributes(t_display, self->window, &wattrib);
	g_assert(ret != BadWindow);
	RECT_SET(self->area, wattrib.x, wattrib.y, wattrib.width, wattrib.height);
	POINT_SET(self->root_pos, wattrib.x, wattrib.y);
	self->border_width = wattrib.border_width;
	wm_debug("client area: %d %d  %d %d  bw %d", wattrib.x, wattrib.y, wattrib.width, wattrib.height, wattrib.border_width);
}

static void client_get_ids(struct wm_client *self)
{
	gboolean got;
	gchar **ss;
	got = T_PROP_GETSS_TYPE(self->window, WM_CLASS, STRING_NO_CC, &ss);
	if(got) {
		if(ss[0]) {
			self->name = g_strdup(ss[0]);
			if(ss[1])
				self->class = g_strdup(ss[1]);
		}
		g_strfreev(ss);
	}
	if(self->name == NULL)
		self->name = g_strdup("");
	if(self->class == NULL)
		self->class = g_strdup("");
}

static void client_save_app_rule_values(struct wm_client *self)
{
	const gchar *type;
	T_PROP_SETS(self->window, LBOXWM_APP_NAME, self->name);
	T_PROP_SETS(self->window, LBOXWM_APP_CLASS, self->class);
	T_PROP_SETS(self->window, LBOXWM_APP_TITLE, self->original_title);
	switch (self->type) {
	case WM_CLIENT_TYPE_NORMAL:
		type = "normal";
		break;
	case WM_CLIENT_TYPE_DIALOG:
		type = "dialog";
		break;
	case WM_CLIENT_TYPE_DESKTOP:
		type = "desktop";
		break;
	}
	T_PROP_SETS(self->window, LBOXWM_APP_TYPE, type);
}

static void client_get_desktop(struct wm_client *self)
{
	GSList *it;
	gboolean first = TRUE;
	guint all = screen_num_desktops;
	for(it = self->parents; it; it = g_slist_next(it)) {
		struct wm_client *c = it->data;
		if(c->desktop == DESKTOP_ALL)
			continue;
		if(first) {
			all = c->desktop;
			first = FALSE;
		} else if(all != c->desktop)
			all = screen_num_desktops;
	}
	if(all != screen_num_desktops) {
		self->desktop = all;
		wm_debug("client desktop set from parents: 0x%x", self->desktop);
	} else {
		self->desktop = screen_desktop;
		wm_debug("client desktop set to the current desktop: %d", self->desktop);
	}
}

static void client_get_shaped(struct wm_client *self)
{
	self->shaped = FALSE;
	if(t_display_extension_shape) {
		gint foo;
		guint ufoo;
		gint s;
		XShapeSelectInput(t_display, self->window, ShapeNotifyMask);
		XShapeQueryExtents(t_display, self->window, &s, &foo, &foo, &ufoo, &ufoo, &foo, &foo, &foo, &ufoo, &ufoo);
		self->shaped = ! !s;
	}
}

static void client_get_colormap(struct wm_client *self)
{
	XWindowAttributes wa;
	if(XGetWindowAttributes(t_display, self->window, &wa))
		client_update_colormap(self, wa.colormap);
}

static void client_get_all(struct wm_client *self, gboolean real)
{
	client_get_area(self);
	client_get_type_and_transientness(self);
	client_update_normal_hints(self);
	client_get_ids(self);
	if(!real)
		return;
	client_update_title(self);
	client_save_app_rule_values(self);
	client_update_protocols(self);
	client_update_wmhints(self);
	if(!self->parents && !self->transient_for_group)
		client_update_transient_for(self);
	client_get_desktop(self);
	client_get_shaped(self);
	{
		if(self->type == WM_CLIENT_TYPE_DESKTOP)
			self->desktop = DESKTOP_ALL;
	}
	client_update_sync_request_counter(self);
	client_get_colormap(self);
	update_strut(self);
}

static struct wm_app_settings *client_get_settings_state(struct wm_client *self)
{
	struct wm_app_settings *settings;
	GSList *it;
	settings = config_create_app_settings();
	for(it = config_per_app_settings; it; it = g_slist_next(it)) {
		struct wm_app_settings *app = it->data;
		gboolean match = TRUE;
		g_assert(app->name != NULL || app->class != NULL || app->title != NULL || (signed)app->type >= 0);
		if(app->name && !g_pattern_match(app->name, strlen(self->name), self->name, NULL))
			match = FALSE;
		else if(app->class && !g_pattern_match(app->class, strlen(self->class), self->class, NULL))
			match = FALSE;
		else if(app->title && !g_pattern_match(app->title, strlen(self->title), self->title, NULL))
			match = FALSE;
		else if((signed)app->type >= 0 && app->type != self->type) {
			match = FALSE;
		}
		if(match) {
			wm_debug("Window matching: %s", app->name);
			config_app_settings_copy_non_defaults(app, settings);
		}
	}
	if(settings->skip_pager != -1)
		self->skip_pager = ! !settings->skip_pager;
	if(settings->skip_taskbar != -1)
		self->skip_taskbar = ! !settings->skip_taskbar;
	if(settings->max_vert != -1)
		self->max_vert = ! !settings->max_vert;
	if(settings->max_horz != -1)
		self->max_horz = ! !settings->max_horz;
	if(settings->fullscreen != -1)
		self->fullscreen = ! !settings->fullscreen;
	if(settings->desktop) {
		if(settings->desktop == DESKTOP_ALL)
			self->desktop = settings->desktop;
		else if(settings->desktop > 0 && settings->desktop <= screen_num_desktops)
			self->desktop = settings->desktop - 1;
	}
	if(settings->layer == -1) {
		self->below = TRUE;
		self->above = FALSE;
	} else if(settings->layer == 0) {
		self->below = FALSE;
		self->above = FALSE;
	} else if(settings->layer == 1) {
		self->below = FALSE;
		self->above = TRUE;
	}
	return settings;
}

static gboolean client_can_steal_focus(struct wm_client *self, gboolean allow_other_desktop, gboolean request_from_user,
	Time steal_time, Time launch_time)
{
	gboolean steal;
	gboolean relative_focused;
	steal = TRUE;
	relative_focused = (focus_client != NULL && (search_focus_tree_full(self) != NULL
			|| search_focus_group_full(self) != NULL));
	wm_debug
		("Want to focus window 0x%x at time %u launched at %u (last user interaction time %u) request from %s, allow other desktop: %s",
		self->window, steal_time, launch_time, event_last_user_time, (request_from_user ? "user" : "other"),
		(allow_other_desktop ? "yes" : "no"));
	if(!launch_time) {
		if(has_relative(self)) {
			if(event_last_user_time && search_focus_group_full(self)) {
				launch_time = event_last_user_time;
				wm_debug("Unknown launch time, using %u - window in active group", launch_time);
			} else if(!request_from_user) {
				launch_time = event_time() - WM_EVENT_USER_TIME_DELAY;
				wm_debug("Unknown launch time, using %u - window in inactive group", launch_time);
			} else {
				launch_time = event_last_user_time;
				wm_debug("Unknown launch time, using %u - user request", launch_time);
			}
		} else {
			launch_time = event_last_user_time;
			wm_debug("Unknown launch time, using %u - independent window", launch_time);
		}
	}
	if(!(self->desktop == screen_desktop || self->desktop == DESKTOP_ALL) && (!allow_other_desktop || (request_from_user
				&& screen_desktop_user_time && !event_time_after(launch_time, screen_desktop_user_time)))) {
		steal = FALSE;
		wm_debug("Not focusing the window because its on another desktop\n");
	} else if(focus_client) {
		if(!relative_focused && event_last_user_time && (event_time_after(event_last_user_time, launch_time)
				&& event_last_user_time != launch_time)
			&& event_time_after(event_last_user_time, steal_time - WM_EVENT_USER_TIME_DELAY)) {
			steal = FALSE;
			wm_debug("Not focusing the window because the user is working in another window that is not its relative");
		} else if(client_focus_target(self) != self) {
			steal = FALSE;
			wm_debug("Not focusing the window because another window would get the focus anyway");
		} else if(!request_from_user) {
			if(has_parent(self) && !relative_focused) {
				steal = FALSE;
				wm_debug("Not focusing the window because it is a transient, and its relatives aren't focused");
			} else if(!(focus_client->can_focus || focus_client->focus_notify)) {
				steal = FALSE;
				wm_debug("Not focusing the window because a globally active client has focus");
			} else if(!allow_other_desktop && !screen_compare_desktops(self->desktop, screen_desktop) && !relative_focused) {
				steal = FALSE;
				wm_debug("Not focusing the window because it is on another desktop and no relatives are focused ");
			}
		}
	}
	if(!steal)
		wm_debug("Focus stealing prevention activated for %s at time %u (last user interaction time %u)", self->title,
			steal_time, event_last_user_time);
	else
		wm_debug("Allowing focus stealing for %s at time %u (last user interaction time %u)", self->title, steal_time,
			event_last_user_time);
	return steal;
}

static void client_apply_startup_state(struct wm_client *self, gint x, gint y, gint w, gint h)
{
	gboolean fullscreen = self->fullscreen;
	gboolean demands_attention = self->demands_attention;
	gboolean max_horz = self->max_horz;
	gboolean max_vert = self->max_vert;
	struct wm_rect oldarea;
	gint l;
	self->fullscreen = self->demands_attention = self->max_horz = self->max_vert = FALSE;
	client_try_configure(self, &x, &y, &w, &h, &l, &l, FALSE);
	wm_debug("placed window 0x%x at %d, %d with size %d x %d", self->window, x, y, w, h);
	oldarea = self->area;
	RECT_SET(self->area, x, y, w, h);
	if(demands_attention)
		client_hilite(self, TRUE);
	if(max_vert && max_horz)
		client_maximize(self, TRUE, 0);
	else if(max_vert)
		client_maximize(self, TRUE, 2);
	else if(max_horz)
		client_maximize(self, TRUE, 1);
	if(fullscreen)
		client_fullscreen(self, TRUE);
	self->area = oldarea;
	client_configure(self, x, y, w, h, FALSE, TRUE, FALSE);
}

static void client_present(struct wm_client *self, gboolean here, gboolean raise)
{
	if(client_normal(self) && screen_showing_desktop)
		screen_show_desktop(FALSE, self);
	if(self->desktop != DESKTOP_ALL && self->desktop != screen_desktop) {
		if(here)
			client_set_desktop(self, screen_desktop, FALSE, TRUE);
		else
			screen_set_desktop(self->desktop, FALSE);
	} else if(!self->frame->visible)
		return;
	if(raise)
		stacking_raise(CLIENT_AS_WINDOW(self));
	client_focus(self);
}

void client_manage(Window window)
{
	struct wm_client *self;
	XSetWindowAttributes attrib_set;
	gboolean try_activate = FALSE;
	gboolean do_activate;
	struct wm_app_settings *settings;
	gboolean transient = FALSE;
	struct wm_rect place;
	Time launch_time;
	guint32 user_time;
	gboolean wm_placed;
	wm_debug("Managing window: 0x%lx", window);
	attrib_set.event_mask = CLIENT_EVENTMASK;
	attrib_set.do_not_propagate_mask = CLIENT_NOPROPAGATEMASK;
	XChangeWindowAttributes(t_display, window, CWEventMask | CWDontPropagate, &attrib_set);
	self = g_slice_new0(struct wm_client);
	self->wm_win.type = WM_WINDOW_CLASS_CLIENT;
	self->window = window;
	self->managed = TRUE;
	self->wm_state = WithdrawnState;
	self->gravity = NorthWestGravity;
	self->desktop = screen_num_desktops;
	client_get_all(self, TRUE);
	wm_debug("Window type: %d", self->type);
	wm_debug("Window group: 0x%x", self->group ? self->group->leader : 0);
	wm_debug("Window name: %s class: %s title: %s", self->name, self->class, self->title);
	settings = client_get_settings_state(self);
	XChangeSaveSet(t_display, window, SetModeInsert);
	self->frame = frame_new(self);
	frame_grab_client(self->frame);
	grab_server(FALSE);
	user_time = event_time();
	focus_order_add_new(self);
	client_calc_layer(self);
	if(wm_state() != WM_STATE_STARTING && ((config_focus_new || settings->focus == 1)
		|| search_focus_tree_full(self)) && (user_time != 0) && settings->focus != 0
		&& focus_valid_target(self, self->desktop, FALSE, FALSE, TRUE, FALSE, settings->focus == 1)) {
		try_activate = TRUE;
	}
	XSetWindowBorderWidth(t_display, self->window, 0);
	frame_adjust_area(self->frame, FALSE, TRUE, FALSE);
	place = self->area;
	wm_debug("Going to try activate new window? %s", try_activate ? "yes" : "no");
	if(try_activate)
		do_activate = client_can_steal_focus(self, settings->focus == 1, (! !launch_time
				|| settings->focus == 1), event_time(), launch_time);
	else
		do_activate = FALSE;
	if(wm_state() == WM_STATE_RUNNING) {
		wm_debug("Positioned: %s @ %d %d",
			(!self->positioned ? "no" : (self->positioned == PPosition ? "program specified" : (self->positioned ==
						USPosition ? "user specified" : (self->positioned ==
							(PPosition | USPosition) ? "program + user specified" : "BADNESS !?")))), place.x, place.y);
		wm_debug("struct wm_sized: %s @ %d %d",
			(!self->sized ? "no" : (self->sized == PSize ? "program specified" : (self->sized ==
						USSize ? "user specified" : (self->sized ==
							(PSize | USSize) ? "program + user specified" : "BADNESS !?")))),
			place.width, place.height);
		wm_placed = place_client(self, do_activate, &place.x, &place.y, settings);
		if(!wm_placed && place.x == 0 && place.y == 0 && client_normal(self) && !client_is_oldfullscreen(self, &place)) {
			struct wm_rect *r;
			r = screen_area(self->desktop, SCREEN_AREA_ALL_MONITORS, NULL);
			if(r->x || r->y) {
				place.x = r->x;
				place.y = r->y;
				wm_debug("Moving buggy app from (0,0) to (%d,%d)", r->x, r->y);
			}
			g_slice_free(struct wm_rect, r);
		}
		client_find_onscreen(self, &place.x, &place.y, place.width, place.height, wm_state() == WM_STATE_RUNNING
			&& (self->type == WM_CLIENT_TYPE_DIALOG || (!((self->positioned & USPosition) || settings->pos_given)
			&& client_normal(self) && !client_is_oldfullscreen(self, &place))));
	}
	if(wm_state() == WM_STATE_RUNNING && (transient || (!(self->sized & USSize
					|| self->positioned & USPosition) && client_normal(self) && !client_is_oldfullscreen(self, &place)))) {
		struct wm_rect *a = screen_area(self->desktop, SCREEN_AREA_ONE_MONITOR, &place);
		place.width += self->frame->size.left + self->frame->size.right;
		place.height += self->frame->size.top + self->frame->size.bottom;
		place.width = MIN(place.width, a->width);
		place.height = MIN(place.height, a->height);
		wm_debug("setting window size to %dx%d", place.width, place.height);
		place.width -= self->frame->size.left + self->frame->size.right;
		place.height -= self->frame->size.top + self->frame->size.bottom;
		g_slice_free(struct wm_rect, a);
	}
	wm_debug("placing window 0x%x at %d, %d with size %d x %d. some restrictions may apply", self->window, place.x, place.y,
		place.width, place.height);
	client_apply_startup_state(self, place.x, place.y, place.width, place.height);
	mouse_grab_for_client(self, TRUE);
	{
		gulong ignore_start;
		if(!config_focus_under_mouse)
			ignore_start = event_start_ignore_all_enters();
		client_show(self);
		if(!config_focus_under_mouse)
			event_end_ignore_all_enters(ignore_start);
	}
	if(try_activate)
		if(do_activate)
			client_present(self, FALSE, TRUE);
		else
			client_hilite(self, TRUE);
	else
		stacking_raise(CLIENT_AS_WINDOW(self));
	client_list = g_list_append(client_list, self);
	window_add(&self->window, CLIENT_AS_WINDOW(self));
	if(STRUT_EXISTS(self->strut))
		screen_update_areas();
	g_slice_free(struct wm_app_settings, settings);
	wm_debug("Managed window 0x%lx plate 0x%x (%s)", window, self->frame->window, self->class);
}

void client_unmanage_all(void)
{
	while(client_list)
		client_unmanage(client_list->data);
}

void client_unmanage(struct wm_client *self)
{
	GSList *it;
	gulong ignore_start;
	wm_debug("Unmanaging window: 0x%x plate 0x%x (%s) (%s)", self->window, self->frame->window, self->class,
		self->title ? self->title : "");
	g_assert(self != NULL);
	XSelectInput(t_display, self->window, NoEventMask);
	if(!config_focus_under_mouse)
		ignore_start = event_start_ignore_all_enters();
	frame_hide(self->frame);
	XFlush(t_display);
	if(!config_focus_under_mouse)
		event_end_ignore_all_enters(ignore_start);
	mouse_grab_for_client(self, FALSE);
	self->managed = FALSE;
	XChangeSaveSet(t_display, self->window, SetModeDelete);
	focus_order_remove(self);
	if(focused(self)) {
		focus_client = NULL;
	}
	client_list = g_list_remove(client_list, self);
	stacking_remove(self);
	window_remove(self->window);
	if(STRUT_EXISTS(self->strut))
		screen_update_areas();
	client_call_notifies(self, client_destroy_notifies);
	for(it = self->parents; it; it = g_slist_next(it))
		((struct wm_client *)it->data)->transients = g_slist_remove(((struct wm_client *)it->data)->transients, self);
	for(it = self->transients; it; it = g_slist_next(it)) {
		((struct wm_client *)it->data)->parents = g_slist_remove(((struct wm_client *)it->data)->parents, self);
		client_calc_layer(it->data);
	}
	if(self->group) {
		group_remove(self->group, self);
		self->group = NULL;
	}
	{
		struct wm_rect a;
		a = self->area;
		if(self->fullscreen)
			a = self->pre_fullscreen_area;
		else if(self->max_horz || self->max_vert) {
			if(self->max_horz) {
				a.x = self->pre_max_area.x;
				a.width = self->pre_max_area.width;
			}
			if(self->max_vert) {
				a.y = self->pre_max_area.y;
				a.height = self->pre_max_area.height;
			}
		}
		self->fullscreen = self->max_horz = self->max_vert = FALSE;
		XSetWindowBorderWidth(t_display, self->window, self->border_width);
		client_move_resize(self, a.x, a.y, a.width, a.height);
	}
	frame_release_client(self->frame);
	frame_free(self->frame);
	self->frame = NULL;
	if(wm_state() != WM_STATE_EXITING) {
		T_PROP_ERASE(self->window, WM_STATE);
	} else {
		XMapWindow(t_display, self->window);
	}
	wm_debug("Unmanaged window 0x%lx", self->window);
	g_slist_free(self->transients);
	g_free(self->startup_id);
	g_free(self->title);
	g_free(self->original_title);
	g_free(self->name);
	g_free(self->class);
	g_slice_free(struct wm_client, self);
}

void client_move_onscreen(struct wm_client *self, gboolean rude)
{
	gint x = self->area.x;
	gint y = self->area.y;
	if(client_find_onscreen(self, &x, &y, self->area.width, self->area.height, rude)) {
		client_move(self, x, y);
	}
}

gboolean client_find_onscreen(struct wm_client *self, gint *x, gint *y, gint w, gint h, gboolean rude)
{
	gint ox = *x, oy = *y;
	gboolean rudel = rude, ruder = rude, rudet = rude, rudeb = rude;
	gint fw, fh;
	struct wm_rect desired;
	guint i;
	gboolean found_mon;
	RECT_SET(desired, *x, *y, w, h);
	frame_rect_to_frame(self->frame, &desired);
	fw = self->frame->size.left + w + self->frame->size.right;
	fh = self->frame->size.top + h + self->frame->size.bottom;
	if(!rude) {
		struct wm_point oldtl, oldtr, oldbl, oldbr;
		struct wm_point newtl, newtr, newbl, newbr;
		gboolean stationary_l, stationary_r, stationary_t, stationary_b;
		POINT_SET(oldtl, self->frame->area.x, self->frame->area.y);
		POINT_SET(oldbr, self->frame->area.x + self->frame->area.width - 1, self->frame->area.y + self->frame->area.height - 1);
		POINT_SET(oldtr, oldbr.x, oldtl.y);
		POINT_SET(oldbl, oldtl.x, oldbr.y);
		POINT_SET(newtl, *x, *y);
		POINT_SET(newbr, *x + fw - 1, *y + fh - 1);
		POINT_SET(newtr, newbr.x, newtl.y);
		POINT_SET(newbl, newtl.x, newbr.y);
		stationary_l = oldtl.x == newtl.x;
		stationary_r = oldtr.x == newtr.x;
		stationary_t = oldtl.y == newtl.y;
		stationary_b = oldbl.y == newbl.y;
		if(stationary_r && newtl.x < oldtl.x)
			rudel = TRUE;
		if(stationary_l && newtr.x > oldtr.x)
			ruder = TRUE;
		if(stationary_b && newtl.y < oldtl.y)
			rudet = TRUE;
		if(stationary_t && newbl.y > oldbl.y)
			rudeb = TRUE;
	}
	found_mon = FALSE;
	for(i = 0; i < screen_num_monitors; ++i) {
		struct wm_rect *a;
		if(!screen_physical_area_monitor_contains(i, &desired)) {
			if(i < screen_num_monitors - 1 || found_mon)
				continue;
			a = screen_area(self->desktop, 0, NULL);
		} else {
			found_mon = TRUE;
			a = screen_area(self->desktop, SCREEN_AREA_ONE_MONITOR, &desired);
		}
		if(client_normal(self)) {
			if(!self->strut.right && *x + fw / 10 >= a->x + a->width - 1)
				*x = a->x + a->width - fw / 10;
			if(!self->strut.bottom && *y + fh / 10 >= a->y + a->height - 1)
				*y = a->y + a->height - fh / 10;
			if(!self->strut.left && *x + fw * 9 / 10 - 1 < a->x)
				*x = a->x - fw * 9 / 10;
			if(!self->strut.top && *y + fh * 9 / 10 - 1 < a->y)
				*y = a->y - fh * 9 / 10;
		}
		if(rudel && !self->strut.left && *x < a->x)
			*x = a->x;
		if(ruder && !self->strut.right && *x + fw > a->x + a->width)
			*x = a->x + MAX(0, a->width - fw);
		if(rudet && !self->strut.top && *y < a->y)
			*y = a->y;
		if(rudeb && !self->strut.bottom && *y + fh > a->y + a->height)
			*y = a->y + MAX(0, a->height - fh);
		g_slice_free(struct wm_rect, a);
	}
	return ox != *x || oy != *y;
}

static void client_update_transient_tree(struct wm_client *self, struct wm_group *oldgroup,
	struct wm_group *newgroup, gboolean oldgtran, gboolean newgtran, struct wm_client *oldparent,
	struct wm_client *newparent)
{
	GSList *it, *next;
	struct wm_client *c;
	g_assert(!oldgtran || oldgroup);
	g_assert(!newgtran || newgroup);
	g_assert((!oldgtran && !oldparent) || (oldgtran && !oldparent) || (!oldgtran && oldparent));
	g_assert((!newgtran && !newparent) || (newgtran && !newparent) || (!newgtran && newparent));
	if(oldgroup == newgroup && oldgtran == newgtran && oldparent == newparent)
		return;
	for(it = self->transients; it; it = next) {
		next = g_slist_next(it);
		c = it->data;
		self->transients = g_slist_delete_link(self->transients, it);
		c->parents = g_slist_remove(c->parents, self);
	}
	for(it = self->parents; it; it = next) {
		next = g_slist_next(it);
		c = it->data;
		self->parents = g_slist_delete_link(self->parents, it);
		c->transients = g_slist_remove(c->transients, self);
	}
	if(newgtran) {
		for(it = newgroup->members; it; it = g_slist_next(it)) {
			c = it->data;
			if(c != self && !client_search_top_direct_parent(c)->transient_for_group && client_normal(c)) {
				c->transients = g_slist_prepend(c->transients, self);
				self->parents = g_slist_prepend(self->parents, c);
			}
		}
	} else if(newparent && !client_is_direct_child(self, newparent) && client_normal(newparent)) {
		newparent->transients = g_slist_prepend(newparent->transients, self);
		self->parents = g_slist_prepend(self->parents, newparent);
	}
	if(!newgtran && newgroup && (!newparent || !client_search_top_direct_parent(newparent)->transient_for_group)
		&& client_normal(self)) {
		for(it = newgroup->members; it; it = g_slist_next(it)) {
			c = it->data;
			if(c != self && c->transient_for_group && !client_is_direct_child(c, self)) {
				self->transients = g_slist_prepend(self->transients, c);
				c->parents = g_slist_prepend(c->parents, self);
			}
		}
	}
	for(it = self->transients; it; it = g_slist_next(it)) {
		c = it->data;
		if(!c->transient_for_group)
			client_update_transient_tree(c, c->group, c->group, c->transient_for_group, c->transient_for_group,
				client_direct_parent(c), client_direct_parent(c));
	}
}

void client_update_transient_for(struct wm_client *self)
{
	Window t = None;
	struct wm_client *target = NULL;
	gboolean trangroup = FALSE;
	if(XGetTransientForHint(t_display, self->window, &t)) {
		if(t != self->window) {
			struct wm_window *tw = window_find(t);
			g_assert(tw != CLIENT_AS_WINDOW(self));
			if(tw && WINDOW_IS_CLIENT(tw)) {
				target = WINDOW_AS_CLIENT(tw);
			}
		}
		if(!target && self->group && t == DefaultRootWindow(t_display))
			trangroup = TRUE;
	} else if(self->group && self->transient)
		trangroup = TRUE;
	client_update_transient_tree(self, self->group, self->group, self->transient_for_group, trangroup,
		client_direct_parent(self), target);
	self->transient_for_group = trangroup;
}

void client_get_type_and_transientness(struct wm_client *self)
{
	Window t;
	self->type = -1;
	self->transient = FALSE;

	if(XGetTransientForHint(t_display, self->window, &t))
		self->transient = TRUE;
	if(self->type == (enum wm_client_type)-1) {
		if(self->transient)
			self->type = WM_CLIENT_TYPE_DIALOG;
		else
			self->type = WM_CLIENT_TYPE_NORMAL;
	}
	if(self->type == WM_CLIENT_TYPE_DIALOG) {
		self->transient = TRUE;
	}
}

void client_update_protocols(struct wm_client *self)
{
	guint32 *proto;
	guint num_ret, i;
	self->focus_notify = FALSE;
	self->delete_window = FALSE;
	if(T_PROP_GETA32(self->window, WM_PROTOCOLS, ATOM, &proto, &num_ret)) {
		for(i = 0; i < num_ret; ++i) {
			if(proto[i] == T_PROP_ATOM(WM_DELETE_WINDOW))
				self->delete_window = TRUE;
			else if(proto[i] == T_PROP_ATOM(WM_TAKE_FOCUS))
				self->focus_notify = TRUE;
			else if(proto[i] == T_PROP_ATOM(NET_WM_SYNC_REQUEST))
				self->sync_request = TRUE;
		}
		g_free(proto);
	}
}

void client_update_sync_request_counter(struct wm_client *self)
{
	guint32 i;
	if(T_PROP_GET32(self->window, NET_WM_SYNC_REQUEST_COUNTER, CARDINAL, &i)) {
		XSyncValue val;
		self->sync_counter = i;
		XSyncIntToValue(&val, 0);
		XSyncSetCounter(t_display, self->sync_counter, val);
	} else
		self->sync_counter = None;
}

void client_update_colormap(struct wm_client *self, Colormap colormap)
{
	if(colormap == self->colormap)
		return;
	wm_debug("Setting client %s colormap: 0x%x", self->title, colormap);
	if(focused(self)) {
		screen_install_colormap(self, FALSE);
		self->colormap = colormap;
		screen_install_colormap(self, TRUE);
	} else
		self->colormap = colormap;
}

void client_update_normal_hints(struct wm_client *self)
{
	XSizeHints size;
	glong ret;
	self->min_ratio = 0.0f;
	self->max_ratio = 0.0f;
	SIZE_SET(self->size_inc, 1, 1);
	SIZE_SET(self->base_size, -1, -1);
	SIZE_SET(self->min_size, 0, 0);
	SIZE_SET(self->max_size, G_MAXINT, G_MAXINT);
	if(XGetWMNormalHints(t_display, self->window, &size, &ret)) {
		self->positioned = (size.flags & (PPosition | USPosition));
		self->sized = (size.flags & (PSize | USSize));
		if(size.flags & PWinGravity)
			self->gravity = size.win_gravity;
		if(size.flags & PAspect) {
			if(size.min_aspect.y)
				self->min_ratio = (gfloat)size.min_aspect.x / size.min_aspect.y;
			if(size.max_aspect.y)
				self->max_ratio = (gfloat)size.max_aspect.x / size.max_aspect.y;
		}
		if(size.flags & PMinSize)
			SIZE_SET(self->min_size, size.min_width, size.min_height);
		if(size.flags & PMaxSize)
			SIZE_SET(self->max_size, size.max_width, size.max_height);
		if(size.flags & PBaseSize)
			SIZE_SET(self->base_size, size.base_width, size.base_height);
		if(size.flags & PResizeInc && size.width_inc && size.height_inc)
			SIZE_SET(self->size_inc, size.width_inc, size.height_inc);
		wm_debug("Normal hints: min size (%d %d) max size (%d %d)", self->min_size.width, self->min_size.height,
			self->max_size.width, self->max_size.height);
		wm_debug("size inc (%d %d) base size (%d %d)", self->size_inc.width, self->size_inc.height, self->base_size.width,
			self->base_size.height);
	} else
		wm_debug("Normal hints: not set");
}

void client_update_wmhints(struct wm_client *self)
{
	XWMHints *hints;
	self->can_focus = TRUE;
	if((hints = XGetWMHints(t_display, self->window)) != NULL) {
		gboolean ur;
		if(hints->flags & InputHint)
			self->can_focus = hints->input;
		ur = self->urgent;
		self->urgent = (hints->flags & XUrgencyHint);
		if(self->urgent && !ur)
			client_hilite(self, TRUE);
		else if(!self->urgent && ur && self->demands_attention)
			client_hilite(self, FALSE);
		if(!(hints->flags & WindowGroupHint))
			hints->window_group = None;
		if(hints->window_group != (self->group ? self->group->leader : None)) {
			struct wm_group *oldgroup = self->group;
			if(self->group) {
				group_remove(self->group, self);
				self->group = NULL;
			}
			if(hints->window_group != None) {
				self->group = group_add(hints->window_group, self);
			}
			client_update_transient_tree(self, oldgroup, self->group, self->transient_for_group, self->transient_for_group,
				client_direct_parent(self), client_direct_parent(self));
			if(self->transient && ((self->parents == NULL && oldgroup == NULL) || (self->transient_for_group && !self->group)))
				client_update_transient_for(self);
		}
		XFree(hints);
	}
	focus_cycle_addremove(self, TRUE);
}

void client_update_title(struct wm_client *self)
{
	gchar *data = NULL;
	gchar *visible = NULL;
	g_free(self->title);
	g_free(self->original_title);
	if(!T_PROP_GETS(self->window, WM_NAME, &data)) {
		if(self->transient) {
			data = g_strdup("");
		} else
			data = g_strdup(_("Unnamed Window"));
	}
	self->original_title = g_strdup(data);
	visible = data;
	self->title = visible;
	data = NULL;
	if(!T_PROP_GETS(self->window, WM_ICON_NAME, &data))
		data = g_strdup(self->title);
	visible = data;
}

static void client_change_wm_state(struct wm_client *self)
{
	gulong state[2];
	glong old;
	old = self->wm_state;
	self->wm_state = NormalState;
	if(old != self->wm_state) {
		state[0] = self->wm_state;
		state[1] = None;
		T_PROP_SETA32(self->window, WM_STATE, WM_STATE, state, 2);
	}
}

gboolean client_is_oldfullscreen(const struct wm_client *self, const struct wm_rect *area)
{
	const struct wm_rect *monitor, *allmonitors;
	if(self->max_horz || self->max_vert)
		return FALSE;
	monitor = screen_physical_area_monitor(screen_find_monitor(area));
	allmonitors = screen_physical_area_all_monitors();
	return (RECT_EQUAL(*area, *monitor) || RECT_EQUAL(*area, *allmonitors));
}

static enum wm_stacking_layer calc_layer(struct wm_client *self)
{
	enum wm_stacking_layer l;
	if(self->type == WM_CLIENT_TYPE_DESKTOP)
		l = WM_STACKING_LAYER_DESKTOP;
	else if((self->fullscreen || client_is_oldfullscreen(self, &self->area)) && (focused(self)
			|| search_focus_tree(self) || (self->desktop != screen_desktop && self->desktop != DESKTOP_ALL)
			|| (!focus_client || client_monitor(focus_client) != client_monitor(self))))
		l = WM_STACKING_LAYER_FULLSCREEN;
	else if(self->above)
		l = WM_STACKING_LAYER_ABOVE;
	else if(self->below)
		l = WM_STACKING_LAYER_BELOW;
	else
		l = WM_STACKING_LAYER_NORMAL;
	return l;
}

static void client_calc_layer_recursive(struct wm_client *self, struct wm_client *orig, enum wm_stacking_layer min)
{
	enum wm_stacking_layer old, own;
	GSList *it;
	old = self->layer;
	own = calc_layer(self);
	self->layer = MAX(own, min);
	if(self->layer != old) {
		stacking_remove(CLIENT_AS_WINDOW(self));
		stacking_add_nonintrusive(CLIENT_AS_WINDOW(self));
	}
	self->visited = TRUE;
	for(it = self->transients; it; it = g_slist_next(it))
		client_calc_layer_recursive(it->data, orig, self->layer);
}

static void client_calc_layer_internal(struct wm_client *self)
{
	GSList *sit;
	sit = client_search_all_top_parents(self);
	for(; sit; sit = g_slist_next(sit))
		client_calc_layer_recursive(sit->data, self, 0);
}

void client_calc_layer(struct wm_client *self)
{
	GList *it;
	for(it = stacking_list; it; it = g_list_next(it))
		if(window_layer(it->data) <= WM_STACKING_LAYER_FULLSCREEN)
			break;
	for(; it; it = g_list_next(it)) {
		if(window_layer(it->data) < WM_STACKING_LAYER_FULLSCREEN)
			break;
		else if(WINDOW_IS_CLIENT(it->data))
			WINDOW_AS_CLIENT(it->data)->visited = FALSE;
	}
	client_calc_layer_internal(self);
	for(it = stacking_list; it; it = g_list_next(it))
		if(window_layer(it->data) <= WM_STACKING_LAYER_FULLSCREEN)
			break;
	for(; it; it = g_list_next(it)) {
		if(window_layer(it->data) < WM_STACKING_LAYER_FULLSCREEN)
			break;
		else if(WINDOW_IS_CLIENT(it->data) && !WINDOW_AS_CLIENT(it->data)->visited)
			client_calc_layer_internal(it->data);
	}
}

gboolean client_should_show(struct wm_client *self)
{
	if(client_normal(self) && screen_showing_desktop)
		return FALSE;
	if(self->desktop == screen_desktop || self->desktop == DESKTOP_ALL)
		return TRUE;
	return FALSE;
}

gboolean client_show(struct wm_client *self)
{
	gboolean show = FALSE;
	if(client_should_show(self)) {
		mouse_replay_pointer();
		frame_show(self->frame);
		show = TRUE;
		client_change_wm_state(self);
	}
	return show;
}

gboolean client_hide(struct wm_client *self)
{
	gboolean hide = FALSE;
	if(!client_should_show(self)) {
		mouse_replay_pointer();
		frame_hide(self->frame);
		hide = TRUE;
		client_change_wm_state(self);
	}
	return hide;
}

void client_showhide(struct wm_client *self)
{
	if(!client_show(self))
		client_hide(self);
}

gboolean client_normal(struct wm_client *self)
{
	return self->type != WM_CLIENT_TYPE_DESKTOP;
}

gboolean client_helper(struct wm_client *self)
{
	(void)self;
	return FALSE;
}

gboolean client_mouse_focusable(struct wm_client *self)
{
	(void)self;
	return TRUE;
}

gboolean client_enter_focusable(struct wm_client *self)
{
	return (client_mouse_focusable(self) && self->type != WM_CLIENT_TYPE_DESKTOP);
}

void client_gravity_resize_w(struct wm_client *self, gint *x, gint oldw, gint neww)
{
	g_assert(*x == self->area.x);
	g_assert(oldw == self->area.width);
	switch (self->gravity) {
	default:
	case NorthWestGravity:
	case WestGravity:
	case SouthWestGravity:
	case StaticGravity:
	case ForgetGravity:
		break;
	case NorthGravity:
	case CenterGravity:
	case SouthGravity:
		*x -= (neww - oldw) / 2;
		break;
	case NorthEastGravity:
	case EastGravity:
	case SouthEastGravity:
		*x -= neww - oldw;
		break;
	}
}

void client_gravity_resize_h(struct wm_client *self, gint *y, gint oldh, gint newh)
{
	g_assert(*y == self->area.y);
	g_assert(oldh == self->area.height);
	switch (self->gravity) {
	default:
	case NorthWestGravity:
	case NorthGravity:
	case NorthEastGravity:
	case StaticGravity:
	case ForgetGravity:
		break;
	case WestGravity:
	case CenterGravity:
	case EastGravity:
		*y -= (newh - oldh) / 2;
		break;
	case SouthWestGravity:
	case SouthGravity:
	case SouthEastGravity:
		*y -= newh - oldh;
		break;
	}
}

void client_try_configure(struct wm_client *self, gint *x, gint *y, gint *w, gint *h, gint *logicalw, gint *logicalh,
	gboolean user)
{
	struct wm_rect desired = { *x, *y, *w, *h };
	frame_rect_to_frame(self->frame, &desired);
	frame_adjust_area(self->frame, FALSE, TRUE, TRUE);
	*w = MIN(*w, G_MAXUSHORT - self->frame->size.left - self->frame->size.right);
	*h = MIN(*h, G_MAXUSHORT - self->frame->size.top - self->frame->size.bottom);
	if(self->fullscreen) {
		const struct wm_rect *a;
		guint i;
		i = screen_find_monitor(&desired);
		a = screen_physical_area_monitor(i);
		*x = a->x;
		*y = a->y;
		*w = a->width;
		*h = a->height;
		user = FALSE;
	} else if(self->max_horz || self->max_vert) {
		struct wm_rect *a;
		guint i;
		i = screen_find_monitor(&desired);
		a = screen_area(self->desktop, i, (self->max_horz && self->max_vert ? NULL : &desired));
		if(self->max_horz) {
			*x = a->x;
			*w = a->width - self->frame->size.left - self->frame->size.right;
		}
		if(self->max_vert) {
			*y = a->y;
			*h = a->height - self->frame->size.top - self->frame->size.bottom;
		}
		user = FALSE;
		g_slice_free(struct wm_rect, a);
	}
	{
		gint basew, baseh, minw, minh;
		gint incw, inch, maxw, maxh;
		gfloat minratio, maxratio;
		incw = self->size_inc.width;
		inch = self->size_inc.height;
		minratio = self->fullscreen || (self->max_horz && self->max_vert) ? 0 : self->min_ratio;
		maxratio = self->fullscreen || (self->max_horz && self->max_vert) ? 0 : self->max_ratio;
		if(self->base_size.width >= 0 || self->base_size.height >= 0) {
			basew = self->base_size.width;
			baseh = self->base_size.height;
		} else {
			basew = self->min_size.width;
			baseh = self->min_size.height;
		}
		if(self->min_size.width || self->min_size.height) {
			minw = self->min_size.width;
			minh = self->min_size.height;
		} else {
			minw = self->base_size.width;
			minh = self->base_size.height;
		}
		if(*w > self->max_size.width)
			*w = self->max_size.width;
		if(*w < minw)
			*w = minw;
		if(*h > self->max_size.height)
			*h = self->max_size.height;
		if(*h < minh)
			*h = minh;
		*w -= basew;
		*h -= baseh;
		maxw = *w;
		maxh = *h;
		*w /= incw;
		*h /= inch;
		if(basew + *w < 1)
			*w = 1 - basew;
		if(baseh + *h < 1)
			*h = 1 - baseh;
		*logicalw = incw > 1 ? *w : *w + basew;
		*logicalh = inch > 1 ? *h : *h + baseh;
		*w *= incw;
		*h *= inch;
		if(self->fullscreen || self->max_horz)
			*w = maxw;
		if(self->fullscreen || self->max_vert)
			*h = maxh;
		*w += basew;
		*h += baseh;
		*w -= self->base_size.width;
		*h -= self->base_size.height;
		if(minratio)
			if(*h * minratio > *w) {
				*h = (gint)(*w / minratio);
				if(*h < 1) {
					*h = 1;
					*w = (gint)(*h * minratio);
				}
			}
		if(maxratio)
			if(*h * maxratio < *w) {
				*h = (gint)(*w / maxratio);
				if(*h < 1) {
					*h = 1;
					*w = (gint)(*h * minratio);
				}
			}
		*w += self->base_size.width;
		*h += self->base_size.height;
	}
	g_assert(*w > 0);
	g_assert(*h > 0);
}

void client_configure(struct wm_client *self, gint x, gint y, gint w, gint h, gboolean user, gboolean final,
	gboolean force_reply)
{
	struct wm_rect oldframe, oldclient;
	gboolean send_resize_client;
	gboolean moved = FALSE, resized = FALSE, rootmoved = FALSE;
	gboolean fmoved, fresized;
	gboolean fhorz = self->frame->max_horz;
	gboolean fvert = self->frame->max_vert;
	gint logicalw, logicalh;
	client_try_configure(self, &x, &y, &w, &h, &logicalw, &logicalh, user);
	if(!(w == self->area.width && h == self->area.height))
		SIZE_SET(self->logical_size, logicalw, logicalh);
	moved = (x != self->area.x || y != self->area.y);
	resized = (w != self->area.width || h != self->area.height);
	oldframe = self->frame->area;
	oldclient = self->area;
	RECT_SET(self->area, x, y, w, h);
	send_resize_client = ((!user && resized) || (user && (final || (resized && config_resize_redraw))));
	if(send_resize_client && (w > oldclient.width || h > oldclient.height)) {
		XMoveResizeWindow(t_display, self->window, self->frame->size.left, self->frame->size.top, MAX(w, oldclient.width),
			MAX(h, oldclient.height));
	}
	fmoved = moved;
	fresized = resized;
	if(self->max_horz != fhorz || self->max_vert != fvert) {
		fmoved = fresized = TRUE;
	}
	if(fmoved || fresized) {
		gulong ignore_start;
		if(!user)
			ignore_start = event_start_ignore_all_enters();
		mouse_replay_pointer();
		frame_adjust_area(self->frame, fmoved, fresized, FALSE);
		if(!user)
			event_end_ignore_all_enters(ignore_start);
	}
	if(!user || final) {
		gint oldrx = self->root_pos.x;
		gint oldry = self->root_pos.y;
		POINT_SET(self->root_pos, self->frame->area.x + self->frame->size.left - self->border_width,
			self->frame->area.y + self->frame->size.top - self->border_width);
		if(self->root_pos.x != oldrx || self->root_pos.y != oldry)
			rootmoved = TRUE;
	}
	if((!user && !resized && (rootmoved || force_reply)) || (user && ((!resized && force_reply) || (final && rootmoved)))) {
		XEvent event;
		event.type = ConfigureNotify;
		event.xconfigure.display = t_display;
		event.xconfigure.event = self->window;
		event.xconfigure.window = self->window;
		wm_debug("Sending ConfigureNotify to %s for %d,%d %dx%d", self->title, self->root_pos.x, self->root_pos.y, w, h);
		event.xconfigure.x = self->root_pos.x;
		event.xconfigure.y = self->root_pos.y;
		event.xconfigure.width = w;
		event.xconfigure.height = h;
		event.xconfigure.border_width = self->border_width;
		event.xconfigure.above = None;
		event.xconfigure.override_redirect = FALSE;
		XSendEvent(event.xconfigure.display, event.xconfigure.window, FALSE, StructureNotifyMask, &event);
	}
	if(send_resize_client && (w <= oldclient.width || h <= oldclient.height)) {
		XMoveResizeWindow(t_display, self->window, self->frame->size.left, self->frame->size.top, w, h);
	}
	XFlush(t_display);
	if(self->managed && (screen_find_monitor(&self->frame->area) != screen_find_monitor(&oldframe) || (final
				&& (client_is_oldfullscreen(self, &oldclient) != client_is_oldfullscreen(self, &self->area))))) {
		client_calc_layer(self);
	}
}

void client_fullscreen(struct wm_client *self, gboolean fs)
{
	gint x, y, w, h;
	if(self->fullscreen == fs)
		return;
	self->fullscreen = fs;
	if(fs) {
		self->pre_fullscreen_area = self->area;
		self->pre_fullscreen_max_horz = self->max_horz;
		self->pre_fullscreen_max_vert = self->max_vert;
		if(self->max_horz) {
			self->pre_fullscreen_area.x = self->pre_max_area.x;
			self->pre_fullscreen_area.width = self->pre_max_area.width;
		}
		if(self->max_vert) {
			self->pre_fullscreen_area.y = self->pre_max_area.y;
			self->pre_fullscreen_area.height = self->pre_max_area.height;
		}
		x = self->area.x;
		y = self->area.y;
		w = self->area.width;
		h = self->area.height;
	} else {
		g_assert(self->pre_fullscreen_area.width > 0 && self->pre_fullscreen_area.height > 0);
		self->max_horz = self->pre_fullscreen_max_horz;
		self->max_vert = self->pre_fullscreen_max_vert;
		if(self->max_horz) {
			self->pre_max_area.x = self->pre_fullscreen_area.x;
			self->pre_max_area.width = self->pre_fullscreen_area.width;
		}
		if(self->max_vert) {
			self->pre_max_area.y = self->pre_fullscreen_area.y;
			self->pre_max_area.height = self->pre_fullscreen_area.height;
		}
		x = self->pre_fullscreen_area.x;
		y = self->pre_fullscreen_area.y;
		w = self->pre_fullscreen_area.width;
		h = self->pre_fullscreen_area.height;
		RECT_SET(self->pre_fullscreen_area, 0, 0, 0, 0);
	}
	wm_debug("Window %s going fullscreen (%d)", self->title, self->fullscreen);
	if(fs) {
		client_find_onscreen(self, &x, &y, w, h, FALSE);
	}
	client_move_resize(self, x, y, w, h);
	client_calc_layer(self);
	if(fs) {
		client_focus(self);
	}
}

void client_maximize(struct wm_client *self, gboolean max, gint dir)
{
	gint x, y, w, h;
	g_assert(dir == 0 || dir == 1 || dir == 2);
	if(max) {
		if(dir == 0 && self->max_horz && self->max_vert)
			return;
		if(dir == 1 && self->max_horz)
			return;
		if(dir == 2 && self->max_vert)
			return;
	} else {
		if(dir == 0 && !self->max_horz && !self->max_vert)
			return;
		if(dir == 1 && !self->max_horz)
			return;
		if(dir == 2 && !self->max_vert)
			return;
	}
	x = self->area.x;
	y = self->area.y;
	w = self->area.width;
	h = self->area.height;
	if(max) {
		if((dir == 0 || dir == 1) && !self->max_horz) {
			RECT_SET(self->pre_max_area, self->area.x, self->pre_max_area.y, self->area.width, self->pre_max_area.height);
		}
		if((dir == 0 || dir == 2) && !self->max_vert) {
			RECT_SET(self->pre_max_area, self->pre_max_area.x, self->area.y, self->pre_max_area.width, self->area.height);
		}
	} else {
		if((dir == 0 || dir == 1) && self->max_horz) {
			g_assert(self->pre_max_area.width > 0);
			x = self->pre_max_area.x;
			w = self->pre_max_area.width;
			RECT_SET(self->pre_max_area, 0, self->pre_max_area.y, 0, self->pre_max_area.height);
		}
		if((dir == 0 || dir == 2) && self->max_vert) {
			g_assert(self->pre_max_area.height > 0);
			y = self->pre_max_area.y;
			h = self->pre_max_area.height;
			RECT_SET(self->pre_max_area, self->pre_max_area.x, 0, self->pre_max_area.width, 0);
		}
	}
	if(dir == 0 || dir == 1)
		self->max_horz = max;
	if(dir == 0 || dir == 2)
		self->max_vert = max;
	if(max) {
		client_find_onscreen(self, &x, &y, w, h, FALSE);
	}
	client_move_resize(self, x, y, w, h);
}

void client_close(struct wm_client *self)
{
	if(!self->delete_window)
		XKillClient(t_display, self->window);
	else {
		T_PROP_MSG_TO(self->window, self->window, WM_PROTOCOLS, T_PROP_ATOM(WM_DELETE_WINDOW), event_time(), 0, 0,
			0, NoEventMask);
	}
}

void client_kill(struct wm_client *self)
{
	if(self->pid) {
		if(self->kill_level == 0) {
			wm_debug("killing window 0x%x with pid %lu, with SIGTERM", self->window, self->pid);
			kill(self->pid, SIGTERM);
			++self->kill_level;
			client_update_title(self);
		} else {
			wm_debug("killing window 0x%x with pid %lu, with SIGKILL", self->window, self->pid);
			kill(self->pid, SIGKILL);
		}
	} else {
		XKillClient(t_display, self->window);
	}
}

void client_hilite(struct wm_client *self, gboolean hilite)
{
	if(self->demands_attention == hilite)
		return;
	self->demands_attention = hilite && !focused(self);
	if(self->frame != NULL) {
		if(self->demands_attention) {
			if(self->desktop != screen_desktop && self->desktop != DESKTOP_ALL) {
				stacking_raise(CLIENT_AS_WINDOW(self));
				focus_order_to_top(self);
			}
		}
	}
}

static void client_set_desktop_recursive(struct wm_client *self, guint target, gboolean donthide, gboolean dontraise)
{
	guint old;
	GSList *it;
	if(target != self->desktop && self->type != WM_CLIENT_TYPE_DESKTOP) {
		wm_debug("Setting desktop %u", target + 1);
		g_assert(target < screen_num_desktops || target == DESKTOP_ALL);
		old = self->desktop;
		self->desktop = target;
		if(!donthide)
			client_hide(self);
		client_show(self);
		if(old != DESKTOP_ALL && !dontraise)
			stacking_raise(CLIENT_AS_WINDOW(self));
		if(STRUT_EXISTS(self->strut))
			screen_update_areas();
		else
			client_reconfigure(self, FALSE);
		focus_cycle_addremove(self, FALSE);
	}
	for(it = self->transients; it; it = g_slist_next(it))
		if(it->data != self)
			if(client_is_direct_child(self, it->data))
				client_set_desktop_recursive(it->data, target, donthide, dontraise);
}

void client_set_desktop(struct wm_client *self, guint target, gboolean donthide, gboolean dontraise)
{
	self = client_search_top_direct_parent(self);
	client_set_desktop_recursive(self, target, donthide, dontraise);
	focus_cycle_addremove(NULL, TRUE);
}

gboolean client_is_direct_child(struct wm_client *parent, struct wm_client *child)
{
	while(child != parent && (child = client_direct_parent(child)));
	return child == parent;
}

static struct wm_client *search_modal_child(struct wm_client *self)
{
	GSList *it;
	struct wm_client *ret;
	for(it = self->transients; it; it = g_slist_next(it)) {
		struct wm_client *c = it->data;
		if((ret = search_modal_child(c)))
			return ret;
		if(c->modal)
			return c;
	}
	return NULL;
}

struct wm_client_find_destroy_unmap {
	Window window;
	gint ignore_unmaps;
};
static gboolean find_destroy_unmap(XEvent *e, gpointer data)
{
	struct wm_client_find_destroy_unmap *find = data;
	if(e->type == DestroyNotify)
		return e->xdestroywindow.window == find->window;
	if(e->type == UnmapNotify && e->xunmap.window == find->window)
		return --find->ignore_unmaps < 0;
	return FALSE;
}

gboolean client_validate(struct wm_client *self)
{
	struct wm_client_find_destroy_unmap find;
	XSync(t_display, FALSE);
	find.window = self->window;
	find.ignore_unmaps = self->ignore_unmaps;
	if(xqueue_exists_local(find_destroy_unmap, &find))
		return FALSE;
	return TRUE;
}

struct wm_client *client_focus_target(struct wm_client *self)
{
	struct wm_client *child = NULL;
	child = search_modal_child(self);
	if(child)
		return child;
	return self;
}

gboolean client_can_focus(struct wm_client *self)
{
	self = client_focus_target(self);
	if(!self->frame->visible)
		return FALSE;
	if(!(self->can_focus || self->focus_notify))
		return FALSE;
	return TRUE;
}

static void client_bring_windows_recursive(struct wm_client *self, guint desktop, gboolean helpers, gboolean modals)
{
	GSList *it;
	for(it = self->transients; it; it = g_slist_next(it))
		client_bring_windows_recursive(it->data, desktop, helpers, modals);
	if(((helpers && client_helper(self)) || (modals && self->modal)) && (self->desktop != desktop
				&& self->desktop != DESKTOP_ALL)) {
		client_set_desktop(self, desktop, FALSE, FALSE);
	}
}

static void bring_modal_windows(struct wm_client *self)
{
	client_bring_windows_recursive(self, self->desktop, FALSE, TRUE);
}

gboolean client_focus(struct wm_client *self)
{
	if(!client_validate(self))
		return FALSE;
	bring_modal_windows(self);
	self = client_focus_target(self);
	if(!client_can_focus(self)) {
		wm_debug_type(WM_DEBUG_FOCUS, "Client %s can't be focused", self->title);
		return FALSE;
	}
	wm_debug_type(WM_DEBUG_FOCUS, "Focusing client \"%s\" (0x%x) at time %u", self->title, self->window, event_time());
	event_halt_focus_delay();
	t_display_ignore_errors(TRUE);
	if(self->can_focus) {
		XSetInputFocus(t_display, self->window, RevertToPointerRoot, event_time());
	}
	if(self->focus_notify) {
		XEvent ce;
		ce.xclient.type = ClientMessage;
		ce.xclient.message_type = T_PROP_ATOM(WM_PROTOCOLS);
		ce.xclient.display = t_display;
		ce.xclient.window = self->window;
		ce.xclient.format = 32;
		ce.xclient.data.l[0] = T_PROP_ATOM(WM_TAKE_FOCUS);
		ce.xclient.data.l[1] = event_time();
		ce.xclient.data.l[2] = 0l;
		ce.xclient.data.l[3] = 0l;
		ce.xclient.data.l[4] = 0l;
		XSendEvent(t_display, self->window, FALSE, NoEventMask, &ce);
	}
	t_display_ignore_errors(FALSE);
	wm_debug_type(WM_DEBUG_FOCUS, "Error focusing? %d", t_display_error_occured);
	return !t_display_error_occured;
}

void client_activate(struct wm_client *self, gboolean desktop, gboolean here, gboolean raise, gboolean user)
{
	self = client_focus_target(self);
	if(client_can_steal_focus(self, desktop, user, event_time(), CurrentTime))
		client_present(self, here, raise);
	else
		client_hilite(self, TRUE);
}

void client_bring_helper_windows(struct wm_client *self)
{
	client_bring_windows_recursive(self, self->desktop, TRUE, FALSE);
}

void client_set_layer(struct wm_client *self, gint layer)
{
	if(layer < 0) {
		self->below = TRUE;
		self->above = FALSE;
	} else if(layer == 0) {
		self->below = self->above = FALSE;
	} else {
		self->below = FALSE;
		self->above = TRUE;
	}
	client_calc_layer(self);
}

guint client_monitor(struct wm_client *self)
{
	return screen_find_monitor(&self->frame->area);
}

struct wm_client *client_direct_parent(struct wm_client *self)
{
	if(!self->parents)
		return NULL;
	if(self->transient_for_group)
		return NULL;
	return self->parents->data;
}

struct wm_client *client_search_top_direct_parent(struct wm_client *self)
{
	struct wm_client *p;
	while((p = client_direct_parent(self)))
		self = p;
	return self;
}

static GSList *client_search_all_top_parents_internal(struct wm_client *self, gboolean bylayer,
	enum wm_stacking_layer layer)
{
	GSList *ret;
	struct wm_client *p;
	while((p = client_direct_parent(self)) && (!bylayer || p->layer == layer))
		self = p;
	if(!self->parents)
		ret = g_slist_prepend(NULL, self);
	else
		ret = g_slist_copy(self->parents);
	return ret;
}

GSList *client_search_all_top_parents(struct wm_client *self)
{
	return client_search_all_top_parents_internal(self, FALSE, 0);
}

GSList *client_search_all_top_parents_layer(struct wm_client *self)
{
	return client_search_all_top_parents_internal(self, TRUE, self->layer);
}

struct wm_client *client_search_transient(struct wm_client *self, struct wm_client *search)
{
	GSList *sit;
	for(sit = self->transients; sit; sit = g_slist_next(sit)) {
		if(sit->data == search)
			return search;
		if(client_search_transient(sit->data, search))
			return search;
	}
	return NULL;
}

static void detect_edge(struct wm_rect area, enum wm_direction dir, gint my_head, gint my_size, gint my_edge_start,
	gint my_edge_size, gint *dest, gboolean *near_edge)
{
	gint edge_start, edge_size, head, tail;
	gboolean skip_head = FALSE, skip_tail = FALSE;
	switch (dir) {
	case WM_DIRECTION_NORTH:
	case WM_DIRECTION_SOUTH:
		edge_start = area.x;
		edge_size = area.width;
		break;
	case WM_DIRECTION_EAST:
	case WM_DIRECTION_WEST:
		edge_start = area.y;
		edge_size = area.height;
		break;
	default:
		g_assert_not_reached();
	}
	if(!RANGES_INTERSECT(my_edge_start, my_edge_size, edge_start, edge_size))
		return;
	switch (dir) {
	case WM_DIRECTION_NORTH:
		head = RECT_BOTTOM(area);
		tail = RECT_TOP(area);
		break;
	case WM_DIRECTION_SOUTH:
		head = RECT_TOP(area);
		tail = RECT_BOTTOM(area);
		break;
	case WM_DIRECTION_WEST:
		head = RECT_RIGHT(area);
		tail = RECT_LEFT(area);
		break;
	case WM_DIRECTION_EAST:
		head = RECT_LEFT(area);
		tail = RECT_RIGHT(area);
		break;
	default:
		g_assert_not_reached();
	}
	switch (dir) {
	case WM_DIRECTION_NORTH:
	case WM_DIRECTION_WEST:
		if(my_head <= head + 1)
			skip_head = TRUE;
		if(my_head + my_size - 1 <= tail)
			skip_tail = TRUE;
		if(head + (*near_edge ? 0 : my_size) <= *dest)
			skip_head = TRUE;
		if(tail - (!*near_edge ? 0 : my_size) <= *dest)
			skip_tail = TRUE;
		break;
	case WM_DIRECTION_SOUTH:
	case WM_DIRECTION_EAST:
		if(my_head >= head - 1)
			skip_head = TRUE;
		if(my_head - my_size + 1 >= tail)
			skip_tail = TRUE;
		if(head - (*near_edge ? 0 : my_size) >= *dest)
			skip_head = TRUE;
		if(tail + (!*near_edge ? 0 : my_size) >= *dest)
			skip_tail = TRUE;
		break;
	default:
		g_assert_not_reached();
	}
	wm_debug("my head %d size %d", my_head, my_size);
	wm_debug("head %d tail %d dest %d", head, tail, *dest);
	if(!skip_head) {
		wm_debug("using near edge %d", head);
		*dest = head;
		*near_edge = TRUE;
	} else if(!skip_tail) {
		wm_debug("using far edge %d", tail);
		*dest = tail;
		*near_edge = FALSE;
	}
}

static void find_edge_directional(struct wm_client *self, enum wm_direction dir, gint my_head, gint my_size,
	gint my_edge_start, gint my_edge_size, gint *dest, gboolean *near_edge)
{
	GList *it;
	struct wm_rect *a;
	gint edge;
	guint i;
	a = screen_area(self->desktop, SCREEN_AREA_ALL_MONITORS, &self->frame->area);
	switch (dir) {
	case WM_DIRECTION_NORTH:
		edge = RECT_TOP(*a) - 1;
		break;
	case WM_DIRECTION_SOUTH:
		edge = RECT_BOTTOM(*a) + 1;
		break;
	case WM_DIRECTION_EAST:
		edge = RECT_RIGHT(*a) + 1;
		break;
	case WM_DIRECTION_WEST:
		edge = RECT_LEFT(*a) - 1;
		break;
	default:
		g_assert_not_reached();
	}
	*dest = edge;
	*near_edge = TRUE;
	for(i = 0; i < screen_num_monitors; ++i) {
		struct wm_rect *area = screen_area(self->desktop, i, NULL);
		detect_edge(*area, dir, my_head, my_size, my_edge_start, my_edge_size, dest, near_edge);
		g_slice_free(struct wm_rect, area);
	}
	for(it = client_list; it; it = g_list_next(it)) {
		struct wm_client *cur = it->data;
		if(cur == self)
			continue;
		if(self->desktop != cur->desktop && cur->desktop != DESKTOP_ALL && cur->desktop != screen_desktop)
			continue;
		wm_debug("trying window %s", cur->title);
		detect_edge(cur->frame->area, dir, my_head, my_size, my_edge_start, my_edge_size, dest, near_edge);
	}
	g_slice_free(struct wm_rect, a);
}

void client_find_move_directional(struct wm_client *self, enum wm_direction dir, gint *x, gint *y)
{
	gint head, size;
	gint e, e_start, e_size;
	gboolean near;
	switch (dir) {
	case WM_DIRECTION_EAST:
		head = RECT_RIGHT(self->frame->area);
		size = self->frame->area.width;
		e_start = RECT_TOP(self->frame->area);
		e_size = self->frame->area.height;
		break;
	case WM_DIRECTION_WEST:
		head = RECT_LEFT(self->frame->area);
		size = self->frame->area.width;
		e_start = RECT_TOP(self->frame->area);
		e_size = self->frame->area.height;
		break;
	case WM_DIRECTION_NORTH:
		head = RECT_TOP(self->frame->area);
		size = self->frame->area.height;
		e_start = RECT_LEFT(self->frame->area);
		e_size = self->frame->area.width;
		break;
	case WM_DIRECTION_SOUTH:
		head = RECT_BOTTOM(self->frame->area);
		size = self->frame->area.height;
		e_start = RECT_LEFT(self->frame->area);
		e_size = self->frame->area.width;
		break;
	default:
		g_assert_not_reached();
	}
	find_edge_directional(self, dir, head, size, e_start, e_size, &e, &near);
	*x = self->frame->area.x;
	*y = self->frame->area.y;
	switch (dir) {
	case WM_DIRECTION_EAST:
		if(near)
			e -= self->frame->area.width;
		else
			e++;
		*x = e;
		break;
	case WM_DIRECTION_WEST:
		if(near)
			e++;
		else
			e -= self->frame->area.width;
		*x = e;
		break;
	case WM_DIRECTION_NORTH:
		if(near)
			e++;
		else
			e -= self->frame->area.height;
		*y = e;
		break;
	case WM_DIRECTION_SOUTH:
		if(near)
			e -= self->frame->area.height;
		else
			e++;
		*y = e;
		break;
	default:
		g_assert_not_reached();
	}
}

void client_find_resize_directional(struct wm_client *self, enum wm_direction side, gboolean grow, gint *x, gint *y,
	gint *w, gint *h)
{
	gint head;
	gint e, e_start, e_size, delta;
	gboolean near;
	enum wm_direction dir;
	switch (side) {
	case WM_DIRECTION_EAST:
		head = RECT_RIGHT(self->frame->area) + (self->size_inc.width - 1) * (grow ? 1 : 0);
		e_start = RECT_TOP(self->frame->area);
		e_size = self->frame->area.height;
		dir = grow ? WM_DIRECTION_EAST : WM_DIRECTION_WEST;
		break;
	case WM_DIRECTION_WEST:
		head = RECT_LEFT(self->frame->area) - (self->size_inc.width - 1) * (grow ? 1 : 0);
		e_start = RECT_TOP(self->frame->area);
		e_size = self->frame->area.height;
		dir = grow ? WM_DIRECTION_WEST : WM_DIRECTION_EAST;
		break;
	case WM_DIRECTION_NORTH:
		head = RECT_TOP(self->frame->area) - (self->size_inc.height - 1) * (grow ? 1 : 0);
		e_start = RECT_LEFT(self->frame->area);
		e_size = self->frame->area.width;
		dir = grow ? WM_DIRECTION_NORTH : WM_DIRECTION_SOUTH;
		break;
	case WM_DIRECTION_SOUTH:
		head = RECT_BOTTOM(self->frame->area) + (self->size_inc.height - 1) * (grow ? 1 : 0);
		e_start = RECT_LEFT(self->frame->area);
		e_size = self->frame->area.width;
		dir = grow ? WM_DIRECTION_SOUTH : WM_DIRECTION_NORTH;
		break;
	default:
		g_assert_not_reached();
	}
	wm_debug("head %d dir %d", head, dir);
	find_edge_directional(self, dir, head, 1, e_start, e_size, &e, &near);
	wm_debug("edge %d", e);
	*x = self->frame->area.x;
	*y = self->frame->area.y;
	*w = self->frame->area.width;
	*h = self->frame->area.height;
	switch (side) {
	case WM_DIRECTION_EAST:
		if(grow == near)
			--e;
		delta = e - RECT_RIGHT(self->frame->area);
		*w += delta;
		break;
	case WM_DIRECTION_WEST:
		if(grow == near)
			++e;
		delta = RECT_LEFT(self->frame->area) - e;
		*x -= delta;
		*w += delta;
		break;
	case WM_DIRECTION_NORTH:
		if(grow == near)
			++e;
		delta = RECT_TOP(self->frame->area) - e;
		*y -= delta;
		*h += delta;
		break;
	case WM_DIRECTION_SOUTH:
		if(grow == near)
			--e;
		delta = e - RECT_BOTTOM(self->frame->area);
		*h += delta;
		break;
	default:
		g_assert_not_reached();
	}
	*w -= self->frame->size.left + self->frame->size.right;
	*h -= self->frame->size.top + self->frame->size.bottom;
}

struct wm_client *client_under_pointer(void)
{
	gint x, y;
	GList *it;
	struct wm_client *ret = NULL;
	if(screen_pointer_pos(&x, &y)) {
		for(it = stacking_list; it; it = g_list_next(it)) {
			if(WINDOW_IS_CLIENT(it->data)) {
				struct wm_client *c = WINDOW_AS_CLIENT(it->data);
				if(c->frame->visible && (c->desktop == screen_desktop || c->desktop == DESKTOP_ALL)
					&& RECT_CONTAINS(c->frame->area, x, y)) {
					ret = c;
					break;
				}
			}
		}
	}
	return ret;
}

gboolean client_has_group_siblings(struct wm_client *self)
{
	return self->group && self->group->members->next;
}
